// This file defines the grammar that's used by [Peggy](https://peggyjs.org/) to generate the autocompleteParser.js file.
// The autocompleteParser is setup to parse our custom search syntax and output information needed for autocomplete and syntax highlighting to work.
//
// Here's the general grammar structure:
//
// query: the entry point for the parser and rule to process the values returned by the filterList rule. It takes filters as an argument and returns the final ranges and autocomplete objects.
// filterList: a rule to process information returned by filter rule.
// filter: an abstract rule to simplify the filterList rule. It takes all filter types.
// defaultFilter: a rule to process the default values returned by the defaultKey autocompleteKey. It updates the autocomplete field.
// freeTextFilter: a rule to process text that isn't a filter that needs autocomplete. It resets the autocomplete field (it means the user started a new filter).
// autocompleteKey: a rule to match pre-defined search syntax fields that need autocomplete, e.g. tag, currency, etc.
//
// filter, logicalAnd, operator, alphanumeric, quotedString are defined in baseRules.peggy grammar.
//
{{// CAUTION: DO NOT DIRECTLY ALTER OR MODIFY `searchParser.js` OR `autocompleteParser.js`
// These files are auto-generated by Peggy from grammar files (*.peggy). 
// To make changes, edit the corresponding *.peggy files only.
// Use the `generate-search-parser` and `generate-autocomplete-parser` scripts to regenerate parsers after modifications.
}}
// per-parser initializer (code executed before every parse).
{ 
  let autocomplete = null; 

  // List fields where you cannot prefix it with "-" to negate it
  const nonNegatableKeys = new Set([
    "type", "keyword", "groupCurrency", "groupBy"
  ]);
}

query = _ ranges:filterList? _ { return { autocomplete, ranges }; }

filterList
  = filters:filter|.., logicalAnd| { return filters.filter(Boolean).flat(); }

filter = @(defaultFilter / freeTextFilter)

defaultFilter
  = _ neg:"-"? key:filterKey _ op:filterOperator _ value:identifier? {
      expectingNestedQuote = false; nameOperator = false; // Reset string parser

      const isNegated = !!neg && !nonNegatableKeys.has(key);

      if (!value) {
        autocomplete = {
          key,
          negated: isNegated,
          value: "",
          start: location().end.offset,
          length: 0,
        };
        return;
      }

      autocomplete = {
        key,
        negated: isNegated,
        ...value[value.length - 1],
      };
      return value
        .filter((v) => v.length > 0)
        .map((v) => ({
          key,
          negated: isNegated,
          ...v,
        }));
    }

reportFieldDynamic
  = ("report-field"i / "reportfield"i) "-" rest:$((!([ \t\r\n\xA0,:=<>!]) .)*) {
      const suffix = rest.replaceAll(/^-+/g, "");
      return "reportField-" + (suffix ? suffix : "");
    }
    
freeTextFilter = _ (identifier / ",") _ { autocomplete = null; }

autocompleteKey "key"
  = @(
      in
      / currency
      / groupCurrency
      / total
      / tag
      / category
      / to
      / taxRate
      / from
      / attendee
      / payer
      / exporter
      / createdBy
      / assignee
      / expenseType
      / withdrawalType
      / type
      / status
      / cardID
      / feed
      / groupBy
      / reimbursable
      / billable
      / policyID
      / action
      / date
      / submitted
      / approved
      / paid
      / exported
      / withdrawn
      / posted
      / has
      / is
      / purchaseCurrency
      / purchaseAmount
      / amount
      / merchant
      / description
      / reportID
      / withdrawalID
      / title
      / reportFieldDynamic
    )

filterKey
  = k:autocompleteKey {
      nameOperator = (k === "from" || k === "to" || k === "payer" || k === "exporter" || k === "attendee" || k === "createdBy" || k === "assignee");
      return k;
    }

identifier
  = parts:(quotedString / alphanumeric)|1.., ","| empty:","? {
      const ends = location();
      const value = parts.flat().filter(Boolean); // Filter out undefined values returned by the predicate
      if (empty) {
        value.push("");
      }
      let count = ends.start.offset;
      const result = [];
      value.forEach((filter) => {
        let word = filter;
        if (word.startsWith('"') && word.endsWith('"') && word.length >= 2) {
          word = word.slice(1, -1);
        }
        result.push({
          value: word,
          start: count,
          length: filter.length,
        });
        count += filter.length + 1;
      });
      return result;
    }
